package org.osdtsystem.matrixparser.parsers.output;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.osdtsystem.matrixparser.data.CONLLSentence;
import org.osdtsystem.matrixparser.data.CONLLToken;
import org.osdtsystem.matrixparser.features.TwoWayFeatureHandler;
import org.osdtsystem.matrixparser.parsers.Action;
import org.osdtsystem.matrixparser.parsers.HistoryAction;

public class DependencyTree {
    CONLLSentence sentence;
    int[] heads;
    int[] labels;
    List<List<Integer>> leftChildren = new ArrayList<List<Integer>>();
    List<List<Integer>> rightChildren = new ArrayList<List<Integer>>();
    TwoWayFeatureHandler labelAlphabet;
    int lastComplementId = -1;
    public int lastnode = 0;
    public int leftscan = 0;
    List<HistoryAction> history = new ArrayList<HistoryAction>();

    public CONLLSentence sentence() { return sentence; }
    
    public DependencyTree(CONLLSentence sentence, TwoWayFeatureHandler labelHandler, int lastComplementId) {
        this.labelAlphabet = labelHandler;
        this.sentence = sentence;
        this.lastComplementId = lastComplementId;
        int nodes = sentence.size();
        heads = new int[nodes];
        Arrays.fill(heads, -1);
        labels = new int[nodes];
        for (int i = 0; i < nodes; ++i) {
            leftChildren.add(new ArrayList<Integer>());
            rightChildren.add(new ArrayList<Integer>());
        }
    }

    public int nodes() {
        return sentence.size();
    }
    
    public int time(int node) {
        return node < lastnode ? lastnode - node : leftscan - lastnode;
    }

    public static boolean isLaterThan(int time, int threshold) {
        if (threshold > 0) {
            return time > threshold;
        } else {
            return time > 0 || time < threshold;
        }
    }


    public void setLastnode(int lastnode) { this.lastnode = lastnode; }
    public void setLeftscan(int leftscan) { this.leftscan = leftscan; }

    public int head(int node) {
        if (node < 0)
            return -1;
        return heads[node];
    }
    
    public void setDependency(int dep, int head, int label) {
        if (heads[dep] >= 0)
            removeDependency(dep, heads[dep], labels[dep]);
        if (head < 0) {
            removeDependency(dep, head(dep), label(dep));
        } else {
            heads[dep] = head;
            labels[dep] = label;
            List<Integer> children = dep < head ? leftChildren(head) : rightChildren(head);
            if (! children.contains(dep)) {
                children.add(new Integer(dep));
                Collections.sort(children);
            }
        }
    }

    public void removeDependency(int dep, int head, int label) {
        heads[dep] = -1;
        labels[dep] = 0;
        if (head < 0)
            return;
        List<Integer> children = dep < head ? leftChildren(head) : rightChildren(head);
        if (children.contains(dep))
            children.remove(new Integer(dep));
    }

    public List<Integer> leftChildren(int head) {
        if (head < 0 || head >= leftChildren.size())
            return Collections.emptyList();
        return leftChildren.get(head);
    }

    public List<Integer> rightChildren(int head) {
        if (head < 0 || head >= rightChildren.size())
            return Collections.emptyList();
        return rightChildren.get(head);
    }

    public final int label(int node) {
        if (node < 0 || node >= labels.length)
            return 0;
        return labels[node];
    }

    public final String labelName(int node) {
        return labelAlphabet.getName(label(node));
    }

    public int nextComplement(List<Integer> frame, int first) {
        if (first < 0 || first >= frame.size())
            return -1;
        for (int index = first; index < frame.size(); ++index) {
            if (isComplement(frame.get(index))) {
                return index;
            }
        }
        return -1;
    }

    public int prevComplement(List<Integer> frame, int first) {
        if (first < 0 || first >= frame.size())
            return -1;
        for (int index = first; index >= 0; --index) {
            if (isComplement(frame.get(index))) {
                return index;
            }
        }
        return -1;
    }

    public int adjacentAdjunct(List<Integer> frame, int index) {
        if (index < 0 || index >= frame.size())
            return -1;
        if (isAdjunct(frame.get(index))) {
            return index;
        }
        return -1;
    }

    public boolean isComplement(int node) {
        return isComplementRelation(label(node));
    }

    public boolean isAdjunct(int node) {
        return !isComplementRelation(label(node));
    }

    public boolean isComplementRelation(int label) {
        return lastComplementId >= label;
    }

    public int cframeCount(int head, int time, List<Integer> dependents) {
        int count = 0;
        for (int dep : dependents)
            if (! isLaterThan(dep - head, time) && isComplement(dep))
                count++;
        return count;
    }
    
    public int cframeCount(int head, List<Integer> dependents) {
        return cframeCount(head, Integer.MAX_VALUE, dependents);
    }

    public int[] cframeNodes(int head, int time, List<Integer> dependents) {
        int[] list = new int[cframeCount(head, time, dependents)];
        int i = 0;
        for (int dep : dependents)
            if (! isLaterThan(dep - head, time) && isComplement(dep))
               list[i++] = dep;
        return list;
    }

    public int[] cframeNodes(int head, List<Integer> dependents) {
        return cframeNodes(head, Integer.MAX_VALUE, dependents);
    }

    StringBuilder sb = new StringBuilder();
    public String cframeString(int head, int time, List<Integer> dependents) {
        sb.setLength(0);
        String sep = "";
        for (int comp : cframeNodes(head, time, dependents)) {
            sb.append(sep).append(labelName(comp));
            sep = COMPSEP;
        }
        return sb.toString();
    }

    public String cframeStringLeft(int head, int time) {
        return cframeString(head, time, rightChildren(head));
    }

    public String cframeStringRight(int head, int time) {
        return cframeString(head, time, rightChildren(head));
    }
    
    public String cframeStringAll(int head, int time) {
        return cframeStringLeft(head, time) + HEADSEP
                + cframeStringRight(head, time);
    }

    public boolean dominates(int dominator, int node) {
        return dominates(dominator, node, 0);
    }

    boolean dominates(int dominator, int node, int height) {
        if (node <= 0)
            return false;
        if (height > heads.length)
            return false; // Trapped in a cycle -- just in case, shouldn't happen
        if (node == dominator)
            return height > 0;
        return dominates(dominator, heads[node], height + 1);
    }

    public final static String HEADSEP = "#";
    public final static String COMPSEP = "+";

    public void writeAsDTAG(Writer writer) {
        try {
            writer.append("vars -sloppy\n").append("offset =end\n");
            for (HistoryAction action : history) {
                action.writeAsDTAG(writer, labelAlphabet);
            }
            writer.append("\n");
        } catch (IOException ex) {
                Logger.getLogger(DependencyTree.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void writeAsCONLL(Writer writer) {
        try {
            for (int node = 1; node < nodes(); ++node) {
                CONLLToken token = sentence().get(node);
                writer.append("" + node).append("\t");
                writer.append(token.form()).append("\t");
                writer.append(token.lemma()).append("\t");
                writer.append(token.cpostag()).append("\t");
                writer.append(token.postag()).append("\t");
                writer.append(token.feats()).append("\t");
                writer.append("" + head(node)).append("\t");
                writer.append(labelAlphabet.getName(label(node))).append("\t");
                writer.append("_\t_\t");
                writer.append("\n");
            }
            writer.append("\n");
        } catch (IOException ex) {
                Logger.getLogger(DependencyTree.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    @Override
    public String toString() {
        StringBuilder sbx = new StringBuilder();
        for (int node = 1; node < nodes(); ++node) {
            int label = label(node);
            sbx.append("" + node).append("=").append("" + head(node))
                    .append(":").append(labelAlphabet.getName(label)).append("/").append("" + label)
                    .append(" ");
        }
        return sbx.toString();
    }

    public void addHistory(String name, Action... actions) {
        history.add(new HistoryAction(name, actions));
    }

}
